package davidlauzon.activerecord.connection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Vector;

import android.util.Log;

import davidlauzon.activerecord.ActiveRecord;
import davidlauzon.activerecord.recordset.JDBCRecordSet;
import davidlauzon.activerecord.recordset.RecordSet;
import davidlauzon.activerecord.visitor.SqlSerializer;


public abstract class ConnectionAdapter
{
    /*********************************************************************************************
     * VARIABLES
     *********************************************************************************************/
    protected String _driver = "";
    protected String _dbHost = "localhost";
    protected int    _dbPort;
    protected String _dbName;
    protected String _dbUser = "root";
    protected String _dbPass = "";
    
    protected Connection    _connection;
    protected SqlSerializer _serializer;
    
    protected Vector<String> _bufferedUpdates;
    
    protected int _maxBufferedUpdates = 20;

    
    
    /*********************************************************************************************
     * ONE LINER METHODS
     *********************************************************************************************/
    public String getDriver() { return _driver; }
    public String getDbHost() { return _dbHost; }
    public int    getDbPort() { return _dbPort; }
    public String getDbName() { return _dbName; }
    public String getDbUser() { return _dbUser; }
    public String getDbPass() { return _dbPass; }
    public String getUrl()    { return "jdbc:" + _driver + "://" + _dbHost + ":" + _dbPort + "/" + _dbName; }
    public SqlSerializer getSerializer() { return _serializer; }
    public int    getMaxBufferedUpdates() { return _maxBufferedUpdates; }
    
    public void setDriver(String driver) { _driver = driver; }
    public void setDbHost(String host)   { _dbHost = host; }
    public void setDbPort(int    port)   { _dbPort = port; }
    public void setDbName(String name)   { _dbName = name; }
    public void setDbUser(String user)   { _dbUser = user; }
    public void setDbPass(String pass)   { _dbPass = pass; }
    public void setSerializer(SqlSerializer serializer) { _serializer = serializer; }
    public void setMaxBufferedUpdates( int nb ) { _maxBufferedUpdates = nb; }
    
    
    
    /*********************************************************************************************
     * PUBLIC METHODS
     *********************************************************************************************/
    public ConnectionAdapter(String dbName) {
        _dbName = dbName;
        resetBufferedUpdates();
    }
    
    
    /**
     * @return num_affected_rows or 0
     */
    public int executeUpdate(String sql) throws SQLException {
        long startedAt = System.currentTimeMillis();
        int result = -1;
        Statement statement;
        
        statement = createStatement();
        result    = statement.executeUpdate( sql );
        statement.close();
        logSQL(sql, startedAt);
        
        return result;
    }
    
    
    /**
     * @return last_insert_id or -1 if error occured
     */
    public long executeInsert(String sql) throws SQLException {
        if (executeUpdate(sql) > 0)
            return getLastInsertId();
        else
            return -1;
    }
    
    public void updateLater(String sql) throws SQLException {
        _bufferedUpdates.add(sql);
        if (_bufferedUpdates.size() >= getMaxBufferedUpdates())
            executeAllBufferedUpdates();
    }
    
    public int executeAllBufferedUpdates() throws SQLException
    {
        int size = _bufferedUpdates.size();
        
        long startedAt = System.currentTimeMillis();
        int result = 0;
        Statement statement;
        
        for (int i =0; i< size; i++) {
            statement  = createStatement();
            result    += statement.executeUpdate( _bufferedUpdates.get(i) );
            statement.close();
        }
        
        logSQL( "[" + size + " queries] " + _bufferedUpdates.toString(), startedAt);
        
        resetBufferedUpdates();
        
        return result;
    }
    
    public void resetBufferedUpdates() {
        _bufferedUpdates = new Vector<String>();
    }

    
    public abstract long getLastInsertId() throws SQLException;
    
    public RecordSet executeQuery(String sql) throws SQLException {
        long startedAt = System.currentTimeMillis();
        RecordSet rs = null;
        Statement statement;
        
        statement = createStatement();
        rs        = createRecordSet( statement, statement.executeQuery(sql) );
        logSQL(sql, startedAt);
        
        return rs;
    }
    
    public long executeQueryForLong(String sql) throws SQLException {
        throw new SQLException("executeQueryForLong() currently supported only for Android SQLite database");
    }
    
    public void disconnect() {
        if (_connection != null) {
            try { _connection.close(); } catch (SQLException e) {}
            log("Disconnected from database '" + getDbName() + "'");
        }
        _connection = null;
    }
    
    
    
    /*********************************************************************************************
     * INTERNAL METHODS
     *********************************************************************************************/
    protected void connect() throws SQLException {
        getConnection();
    }
    
    protected Connection getConnection() throws SQLException {
        if (_connection == null) {
            _connection = DriverManager.getConnection(getUrl(), _dbUser, _dbPass);
            log("Connected to database '" + getDbName() + "'.");
        }
        return _connection;
    }
    
    protected Statement createStatement() throws SQLException {
        // Allowing the ResultSet to scroll allows setting the cursor back and forth
        return _connection.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY );
    }
    
    protected RecordSet createRecordSet( Statement statement, ResultSet set ) {
        return new JDBCRecordSet( statement, set );
    }
    
    protected void log(String msg) {
        Log.i(ActiveRecord.LOG_TAG, msg);
    }
    
    protected void logSQL(String sql, long startedAt)
    {
        long ms = System.currentTimeMillis() - startedAt;
        log(String.format("   [SQL (%.3f)]\t%s", ms / 1000F, sql));
    }
}
